AWS CDKで定義したリソースをローカル環境で実行してみた
こんにちは。プロダクトグループのさかいゅです。
CDK(Cloud Development Kit)で実装したリソースをローカル環境で実行できるように、ローカル開発環境について調査しました。
公式ドキュメントにSAM CLIを利用してローカル環境で実行する例を参考にローカル開発環境を構築してみましたので、紹介させていただきます。
バージョン情報
- AWS CLI 1.16.220
- CDK 1.13.1
- SAM CLI 0.22.0
- Docker 19.03.2
- Visual Studio Code 1.39.2
概要
CDKで実装した、API Gateway + Lambda + DynamoDBでサーバーレスな構成のWebAPIをSAM CLIとDynamoDB Localを利用してローカル開発環境を作成します。
プロジェクトの作成
CDKのプロジェクトを作成し、必要なパッケージをインストールします。
# プロジェクトを作成 cdk init --language typescript # 必要なパッケージをインストール npm install --save @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-dynamodb npm install --save-dev @types/node
リソースの定義
今回利用するリソースとDynamoDBにアクセスする単純なLambda関数を実装します。
import cdk = require("@aws-cdk/core"); import lambda = require("@aws-cdk/aws-lambda"); import apigateway = require("@aws-cdk/aws-apigateway"); import dynamodb = require("@aws-cdk/aws-dynamodb"); export class SampleProjectStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const env = process.env.ENV || "local"; // DynamoDBテーブル const sampleTable = new dynamodb.Table(this, "sampleTable", { tableName: "samples", billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, partitionKey: { name: "id", type: dynamodb.AttributeType.STRING } }); // Lambda関数 const sampleLambda = new lambda.Function(this, "sampleLambda", { runtime: lambda.Runtime.NODEJS_8_10, handler: "index.handler", code: lambda.Code.asset("src/lambda/sample"), timeout: cdk.Duration.seconds(60), environment: { ENV: env } }); sampleTable.grantReadWriteData(sampleLambda); // API Gateway const sampleApi = new apigateway.RestApi(this, "sampleApi", { restApiName: "sampleApi" }); const samplesResource = sampleApi.root.addResource("samples"); const getSamplesIntegration = new apigateway.LambdaIntegration(sampleLambda); samplesResource.addMethod("GET", getSamplesIntegration); } }
const AWS = require("aws-sdk"); exports.handler = async event => { var dynamodbOption = {}; if (process.env.ENV === "local") { dynamodbOption = { endpoint: "http://dynamodb:8000", region: "local", accessKeyId: "local", secretAccessKey: "local" }; } var docClient = new AWS.DynamoDB.DocumentClient(dynamodbOption); var params = { TableName: "samples" }; var scanResult = await docClient.scan(params).promise(); var responseBody = { samples: scanResult.Items }; const response = { statusCode: 200, body: JSON.stringify(responseBody) }; return response; };
デプロイ
TypeScriptをコンパイルして、デプロイコマンドを実行することで、AWSにデプロイすることができます。Lambda関数内で、ENV
を参照しlocal
の場合、DynamoDB Localのエンドポイントを指定するように実装していますので、AWSにデプロイする時は、local
以外の値を指定します。
npm run build ENV=dev cdk deploy
ローカル実行
ここまでで、リソースをAWSにデプロイすることができました。ここから、ローカル環境で実行できるように設定していきます。
Dockerのネットワーク設定
SAM CLI、DynamoDB Localのいずれも、Dockerコンテナで実行されます。Dockerコンテナで実行しているLambdaからDockerコンテナで実行しているDynamoDB Localに接続するために、同じネットワーク設定を利用する必要があります。今回はsam-cli
という名前で作成しました。(任意の名前でOKです)
docker network create sam-cli
DynamoDB Local
DynamoDB Localは、公式のDockerイメージがありますので、Dockerを使って構築します。デフォルトで起動するとInMemoryで起動されるためコンテナを停止すると、テーブル定義やデータが消えてしまいます。毎回テーブルの作成やデータを入れ直すのは効率が悪いので、今回はデータを永続化するように設定しました。
version: "3.7" services: dynamodb: image: amazon/dynamodb-local container_name: dynamodb ports: - 8000:8000 command: -jar DynamoDBLocal.jar -dbPath /data volumes: - $PWD/dynamodb/data:/data networks: - sam-cli networks: sam-cli: external: true
docker-compose up -d
テーブルの作成とデータの作成
サンプルテーブルと適当なデータをDynamoDB Localに作成します。
{ "AttributeDefinitions": [ { "AttributeName": "Id", "AttributeType": "S" } ], "TableName": "samples", "KeySchema": [ { "AttributeName": "Id", "KeyType": "HASH" } ], "ProvisionedThroughput": { "ReadCapacityUnits": 1, "WriteCapacityUnits": 1 } }
{ "samples": [ { "PutRequest": { "Item": { "Id": { "S": "sample1" }, "Name": { "S": "HogeHoge" } } } }, { "PutRequest": { "Item": { "Id": { "S": "sample2" }, "Name": { "S": "FooFoo" } } } } ] }
DynamoDB Localは、ACCESS_KEY_IDやリージョン名を利用して、ファイルを保存するようなので、テーブルを作成する前に、環境変数を設定しておきます。(リージョン名はプログラムの指定と合っていれば何でもOKです)
export AWS_SECRET_ACCESS_KEY=local export AWS_ACCESS_KEY_ID=local export AWS_DEFAULT_REGION=local aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://dynamodb/samples.json aws dynamodb batch-write-item --endpoint-url http://localhost:8000 --request-items file://dynamodb/samples_data.json
SAM CLI
cdk synth
コマンドでCloudFormationのテンプレートファイルを作成することができます。ここは、公式ドキュメントに記載されている通りに進めます。
cdk synth --no-staging > template.yaml
API Gatewayを実行
生成したCloudFormationのテンプレートファイルを利用して、APIを実行します。DynamoDB Localに接続する処理を実行する場合は、--docker-network
オプションを指定してください。
$ sam local start-api -t template.yaml --docker-network sam-cli Mounting sampleLambdaXXXXX at http://127.0.0.1:3000/samples [GET] You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template 2019-10-25 00:31:58 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
もちろんブラウザでアクセスすることができます。
Lambda関数を実行
Lambda関数を直接実行することもできます。API Gateway実行時と同様に、必要に応じて--docker-network
オプションを指定してください。また、Lambdaの関数名はtemplate.yaml
を参照して設定してください。
… sampleLambdaXXXXX: Type: AWS::Lambda::Function Properties: Code: S3Bucket: …
$ sam local invoke sampleLambdaXXXXX --no-event --docker-network sam-cli Invoking index.handler (nodejs8.10) 2019-10-25 00:42:50 Found credentials in environment variables. Fetching lambci/lambda:nodejs8.10 Docker container image...... Mounting /XXXX/XXXX/SampleProject/src/lambda/sample as /var/task:ro,delegated inside runtime container START RequestId: XXXX-YYYY Version: $LATEST END RequestId: XXXX-YYYY REPORT RequestId: XXXX-YYYY Duration: 297.76 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 44 MB {"statusCode":200,"body":"{\"samples\":[{\"Id\":\"sample1\",\"Name\":\"HogeHoge\"},{\"Id\":\"sample2\",\"Name\":\"FooFoo\"}]}"}
デバッグ
SAM CLIを利用してLambda関数をデバッグすることができます。今回はVisual Studio Codeを利用して、デバッグしてみます。
デバッグ設定を作成して、--debug-port
を指定してLambda関数を実行します。
{ "version": "0.2.0", "configurations": [ { "name": "CDK + SAM CLIでデバッグ", "type": "node", "request": "attach", "address": "localhost", "port": 5858, "localRoot": "${workspaceRoot}/src/lambda/sample", "remoteRoot": "/var/task", "protocol": "inspector", "stopOnEntry": false } ] }
sam local invoke sampleLambdaXXXXX --no-event --docker-network sam-cli --debug-port 5858
ブレークポイントで止まりました。成功ですね。通常のデバッグと同様に変数の中身を確認したり、ステップ実行をすることができます。
注意事項
デバッグを実行する際に、ランタイムの設定がNode.jsの10系だとaws-sdkモジュールがImportModuleErrorとなり実行できませんでした。なんらかの回避策があるかもしれませんが、今回はNode.jsの8系で実行しています。
さいごに
テストはAWSにデプロイして実施するべきですが、デプロイする前の動作確認には十分使える環境だと思いました。 AWSリソースをローカルで実行する方法の1つとして参考になれば幸いです。